﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Wordprocessing;
using DocumentFormat.OpenXml.Packaging;
using System.Reflection;
using intelliEssay.Interop;

namespace intelliEssay.Interop.ParagraphStyle
{
    /// <summary>
    /// contains help methods for a paragraph's indentation
    /// </summary>
    public static class ParagraphIndentation
    {
        ///<summary>Gets the indentation of a paragraph
        ///</summary>
        ///<remarks>
        /// To get a well-initialized indentation, the following places are searched:
        /// NumberingProperties : Direct, no climbling style hierachy, ignoring styleLink,numberingStyleLink, word seems to be ignoring these elements ,Pr=1
        /// ParagraphStyleId : Indirect, climbling style hierachy,ignoring linked styles->Word seems to be ignoring this element,Pr=2
        /// ParagraphProperties itself : Direct, Pr=0
        /// Previous sibling's ParagraphProperties: Indirect, test for NextParagraph's value and climb style hierachy, Pr=3
        /// Document specific defaults: Direct, Pr=4
        /// Application specific defaults: Direct, Pr=5
        /// ParagraphStyles Specified Num's pPr. Pr=2.5
        /// ParagraphStyleDefault 3.5
        /// NOTE:::Pr=Priority Value, if values are in conflict, lower priority superceds higher priority.
        /// </remarks>>
        /// <param name="propertyName"></param>
        /// <param name="element"></param>
        /// <param name="wd"></param>
        /// <returns></returns>
        public static Indentation GetHierachicalParagraphIndentationEx(Paragraph element, WordprocessingDocument wd, HostApplications ha = HostApplications.MsOffice2010)
        {
            return ParagraphPropertiesHelper.QParagraphPropertiesMultiValueProperty<Indentation>(element, wd, ha);
        }
    }
    /// <summary>
    /// contains help methods for ParagraphProperties's member
    /// </summary>
    public static class ParagraphPropertiesHelper
    {
        internal static IEnumerable<Style> FastGetFullStyles(WordprocessingDocument wd, HostApplications ha)
        {
            IEnumerable<Style> styles = DocumentElementPropertyCache.WordDocPropertyCache.GetProperty(wd, "Style-Full");
            if (styles == null)
            {
                styles = (from style in wd.MainDocumentPart.StyleDefinitionsPart.Styles.ChildElements where style.LocalName == "style" select style).Cast<Style>();
                if (ha == HostApplications.MsOffice2010)
                {
                    try
                    {
                        styles = styles.Union<Style>(wd.MainDocumentPart.StylesWithEffectsPart.Styles.ChildElements.Where<OpenXmlElement>(
                        o =>
                        {
                            return o.LocalName == "style";
                        }).Cast<Style>(), new StyleComparer());
                    }
                    catch
                    {
                    }
                    //  styles.Count();
                }
                DocumentElementPropertyCache.WordDocPropertyCache.StoreProperty(wd, "Style-Full", styles);
            }
            return styles;
        }
        internal static Dictionary<string, Style> FastGetStyleDict(WordprocessingDocument wd, HostApplications ha)
        {
            Dictionary<string, Style> StyleDictionary = DocumentElementPropertyCache.WordDocPropertyCache.GetProperty(wd, "Style-Dict");
            if (StyleDictionary == null)
            {
                StyleDictionary = new Dictionary<string, Style>();
                var styles = FastGetFullStyles(wd, ha);
                foreach (Style style in styles)
                {
                    StyleDictionary.Add(style.StyleId.Value, style);
                }
                DocumentElementPropertyCache.WordDocPropertyCache.StoreProperty(wd, "Style-Dict", StyleDictionary);
            }
            return StyleDictionary;

        }
        private static dynamic ParagraphPropertiesIntegratedQuery(string propertyName, Paragraph element, WordprocessingDocument wd, bool isMultiValue, HostApplications ha = HostApplications.MsOffice2010)
        {
            List<Value_Priority_Pair> vpp = new List<Value_Priority_Pair>();
            DocDefaults dd = wd.MainDocumentPart.StyleDefinitionsPart.Styles.DocDefaults;
            IEnumerable<Style> styles = FastGetFullStyles(wd, ha);
            Dictionary<string, Style> styleDict = FastGetStyleDict(wd, ha);
            dynamic result = null;
            if (element.ParagraphProperties == null)
            {
                element.ParagraphProperties = new ParagraphProperties();
            }

            Numbering numbering = null;
            try
            {
                numbering = wd.MainDocumentPart.NumberingDefinitionsPart.Numbering;
            }
            catch
            { }
            IEnumerable<AbstractNum> abstrNumCollection = null;
            IEnumerable<NumberingInstance> instNumCollection = null;
            if (numbering != null)
            {
                abstrNumCollection = numbering.ChildElements.Where<OpenXmlElement>(o =>
                {
                    return o.GetType() == typeof(AbstractNum);
                }).Cast<AbstractNum>();
                instNumCollection = numbering.ChildElements.Where<OpenXmlElement>(o =>
                {
                    return o.GetType() == typeof(NumberingInstance);
                }).Cast<NumberingInstance>();
            }
            #region Pr0
            var temp = element.ParagraphProperties;
            result = temp.GetType().GetProperty(propertyName).GetValue(temp, null);
            if (result != null)
            {
                if (isMultiValue)
                {
                    vpp.Add(new Value_Priority_Pair() { value = result, priority = 0 });
                }
                else
                {
                    return result;
                }
            }
            #endregion Pr0
            #region Pr1
            if (element.ParagraphProperties.NumberingProperties != null)
            {
                var correspondingLevel = ParagraphLevel.GetParagraphLevel(element, wd);
                if (correspondingLevel != null)
                    if (correspondingLevel.PreviousParagraphProperties != null)
                    {
                        var pPr = correspondingLevel.PreviousParagraphProperties;
                        result = pPr.GetType().GetProperty(propertyName).GetValue(pPr, null);
                        if (result != null)
                        {
                            if (isMultiValue)
                            {
                                vpp.Add(new Value_Priority_Pair() { value = result, priority = 1 });
                            }
                            else
                            {
                                return result;
                            }
                        }
                    }
            }
            #endregion
            #region Pr2
            if (element.ParagraphProperties.ParagraphStyleId != null)
            {
                string val = element.ParagraphProperties.ParagraphStyleId.Val;
                Style s = styleDict[val];
                if (isMultiValue)
                {
                    result = WalkStyleHierachyForMultiValueProperty(propertyName, s, styleDict, true);
                }
                else
                {
                    result = WalkStyleHierachyForSingleValueProperty(propertyName, s, styleDict, true);
                }
                if (result != null)
                {
                    if (isMultiValue)
                    {
                        vpp.Add(new Value_Priority_Pair() { value = result, priority = 2 });
                    }
                    else
                    {
                        return result;
                    }
                }
            }
            #endregion
            #region Pr2.5
            if (element.ParagraphProperties.ParagraphStyleId != null && numbering != null)
            {
                string val = element.ParagraphProperties.ParagraphStyleId.Val;
                Style style = styleDict[val];
                if (style.StyleParagraphProperties != null)
                {
                    if (style.StyleParagraphProperties.NumberingProperties != null)
                    {
                        var numProperty = style.StyleParagraphProperties.NumberingProperties;
                        int ilvl = 0;
                        int numId = -1;
                        try
                        {
                            ilvl = numProperty.NumberingLevelReference.Val;
                            numId = numProperty.NumberingId.Val;
                        }
                        catch (NullReferenceException)
                        {
                        }
                        NumberingInstance numIns = instNumCollection.FirstOrDefault<NumberingInstance>(o =>
                        {
                            return o.NumberID.Value == numId;
                        });
                        if (numIns != null)
                        {
                            int abstId = numIns.AbstractNumId.Val;
                            AbstractNum abstNum = abstrNumCollection.FirstOrDefault<AbstractNum>(o =>
                            {
                                return o.AbstractNumberId.Value == abstId;
                            });
                            if (abstNum != null)
                            {
                                var Level = abstNum.ChildElements.FirstOrDefault<OpenXmlElement>(o =>
                                {
                                    if (o.LocalName != "lvl")
                                    {
                                        return false;
                                    }
                                    else
                                    {
                                        Level l = o as Level;
                                        if (l.LevelIndex.Value == ilvl)
                                        {
                                            return true;
                                        }
                                    }
                                    return false;
                                }) as Level;
                                if (Level != null)
                                {
                                    if (Level.PreviousParagraphProperties != null)
                                    {
                                        result = Level.PreviousParagraphProperties.GetType().GetProperty(propertyName).GetValue(Level.PreviousParagraphProperties, null);
                                        if (result != null)
                                        {
                                            if (isMultiValue)
                                            {
                                                vpp.Add(new Value_Priority_Pair() { value = result, priority = 2.5 });
                                            }
                                            else
                                            {
                                                return result;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

            #endregion
            #region Pr3
            Paragraph prevSiblingLeagle = element.PreviousSibling<Paragraph>();
            if (prevSiblingLeagle != null)
            {
                if (prevSiblingLeagle.ParagraphProperties != null)
                {
                    if (prevSiblingLeagle.ParagraphProperties.ParagraphStyleId != null)
                    {
                        string refId = prevSiblingLeagle.ParagraphProperties.ParagraphStyleId.Val;
                        Style s = styleDict[refId];
                        if (s.NextParagraphStyle != null)
                        {
                            refId = s.NextParagraphStyle.Val;
                            if (isMultiValue)
                            {
                                result = WalkStyleHierachyForMultiValueProperty(propertyName, s, styleDict, false);
                            }
                            else
                            {
                                result = WalkStyleHierachyForSingleValueProperty(propertyName, s, styleDict, false);
                            }
                            if (result != null)
                            {
                                if (isMultiValue)
                                {
                                    vpp.Add(new Value_Priority_Pair() { value = result, priority = 3 });
                                }
                                else
                                {
                                    return result;
                                }
                            }
                        }
                    }
                }
            }
            #endregion
            #region Pr3.25
            Table tb = null;
            try
            {
                tb = element.Parent.Parent.Parent as Table;
            }
            catch
            {
            }
            if (tb != null)
            {
                TableProperties tp = tb.ChildElements.FirstOrDefault<OpenXmlElement>(o =>
                {
                    return o.LocalName == "tblPr";
                }) as TableProperties;
                if (tp != null)
                {
                    if (tp.TableStyle != null)
                    {
                        Style s = styleDict[tp.TableStyle.Val];
                        if (isMultiValue)
                        {
                            result = WalkStyleHierachyForMultiValueProperty(propertyName, s, styleDict, true);
                        }
                        else
                        {
                            result = WalkStyleHierachyForSingleValueProperty(propertyName, s, styleDict, true);
                        }
                        if (result != null)
                        {
                            vpp.Add(new Value_Priority_Pair() { value = result, priority = 3 });
                        }
                        else
                        {
                            return result;
                        }
                    }
                }
            }
            #endregion
            #region Pr5
            result = DocumentSpecificDefault.GetDefaultOrSettingByPropertyName(propertyName, null, wd, ha);
            if (result != null)
            {
                if (isMultiValue)
                {
                    vpp.Add(new Value_Priority_Pair() { value = result, priority = 5 });
                }
                else
                {
                    return result;
                }
            }
            Indentation ind;
            #endregion
            InitializationServices.MergeProperties(ref result, vpp);
            return result;
        }
        /// <summary>
        /// A shorthand wrapper that provides generic support for retrieving multiple properties related to paragraph
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="propertyName"></param>
        /// <param name="element"></param>
        /// <param name="wd"></param>
        /// <param name="ha"></param>
        /// <returns></returns>
        public static T QParagraphPropertiesMultiValueProperty<T>(Paragraph element, WordprocessingDocument wd, HostApplications ha = HostApplications.MsOffice2010)
        {
            return (T)QParagraphPropertiesMultiValueProperty(typeof(T).Name,element, wd, ha);
        }
        /// <summary>
        /// A shorthand wrapper for non-generic version of QParagraphPropertiesSingleValueProperty.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="propertyName"></param>
        /// <param name="element"></param>
        /// <param name="wd"></param>
        /// <param name="ha"></param>
        /// <returns></returns>
        public static T QParagraphPropertiesSingleValueProperty<T>(Paragraph element, WordprocessingDocument wd, HostApplications ha = HostApplications.MsOffice2010)
        {
            return (T)QParagraphPropertiesSingleValueProperty(typeof(T).Name, element, wd, ha);
        }
        /// <summary>
        /// intelligent*:Get a certain multi-value in a paragraph's paragraph property.
        /// </summary>
        /// <remarks>
        /// 所谓多值，是指在段落属性中不包含Val的类型。这些值包括:
        /// <para>ind</para>
        /// <para>tabs</para>
        /// <para>rPr</para>
        /// </remarks>
        /// <param name="propertyName">the name of the property</param>
        /// <param name="element">the paragraph whose paragraphproperties is searched</param>
        /// <param name="wd"></param>
        /// <param name="ha">the type of host application</param>
        /// <returns></returns>
        public static dynamic QParagraphPropertiesMultiValueProperty(string propertyName, Paragraph element, WordprocessingDocument wd, HostApplications ha = HostApplications.MsOffice2010)
        {
            return ParagraphPropertiesIntegratedQuery(propertyName, element, wd, true, ha);
        }
        /// <summary>
        /// intelligent*:Get a certain single-value in a paragraph's paragraph property.
        /// </summary>
        /// <remarks>
        /// 所谓单值，是指在段落属性中包含Val的类型。这些值为ParagraphProperties中所有除了以下类型的类型:
        /// <para>ind</para>
        /// <para>tabs</para>
        /// <para>rPr</para>
        /// </remarks>
        /// <param name="propertyName">the name of the property</param>
        /// <param name="element">the paragraph whose paragraphproperties is searched</param>
        /// <param name="wd"></param>
        /// <param name="ha">the type of host application</param>
        /// <returns></returns>
        public static dynamic QParagraphPropertiesSingleValueProperty(string propertyName, Paragraph element, WordprocessingDocument wd, HostApplications ha = HostApplications.MsOffice2010)
        {
            return ParagraphPropertiesIntegratedQuery(propertyName, element, wd, false, ha);
        }
        private static dynamic WalkStyleHierachyForMultiValueProperty(string propertyName, Style style, Dictionary<string, Style> paraStyleDict, bool Inclusvie)
        {
            Style current = style;
            string styleID;
            List<Value_Priority_Pair> vpp = new List<Value_Priority_Pair>();
            int pri = 0;
            dynamic result = null;
            if (Inclusvie)
                if (current.StyleParagraphProperties != null)
                {
                    StyleParagraphProperties styleP = style.StyleParagraphProperties;
                    if (styleP != null)
                    {
                        result = styleP.GetType().GetProperty(propertyName).GetValue(styleP, null);
                        if (result != null)
                        {
                            vpp.Add(new Value_Priority_Pair() { value = result, priority = pri++ });
                        }
                    }
                }
            while (current.BasedOn != null)
            {
                styleID = current.BasedOn.Val;
                current = paraStyleDict[styleID];
                if (current.StyleParagraphProperties != null)
                {
                    StyleParagraphProperties styleP = current.StyleParagraphProperties;
                    if (styleP == null)
                    {
                        continue;
                    }
                    result = styleP.GetType().GetProperty(propertyName).GetValue(styleP, null);
                    if (result != null)
                    {
                        vpp.Add(new Value_Priority_Pair() { value = result, priority = pri++ });
                    }
                }
            }
            InitializationServices.MergeProperties(ref result, vpp.ToArray());
            return result;
        }
        private static dynamic WalkStyleHierachyForSingleValueProperty(string propertyName, Style style, Dictionary<string, Style> paraStyleDict, bool Inclusive)
        {
            Style current = style;
            string styleID;
            dynamic result = null;
            if (Inclusive)
                if (current.StyleParagraphProperties != null)
                {
                    StyleParagraphProperties styleP = style.StyleParagraphProperties;
                    if (styleP != null)
                    {
                        result = styleP.GetType().GetProperty(propertyName).GetValue(styleP, null);
                        if (result != null)
                        {
                            return result;
                        }
                    }
                }
            while (current.BasedOn != null)
            {
                styleID = current.BasedOn.Val;
                current = paraStyleDict[styleID];
                if (current.StyleParagraphProperties != null)
                {
                    StyleParagraphProperties styleP = current.StyleParagraphProperties;
                    if (styleP == null)
                    {
                        continue;
                    }
                    result = styleP.GetType().GetProperty(propertyName).GetValue(styleP, null);
                    if (result != null)
                    {
                        return result;
                    }
                }
            }
            return result;
        }
    }
    /// <summary>
    /// The level of a paragraph
    /// </summary>
    public static class ParagraphLevel
    {
        /// <summary>
        /// Calculates the level of a certain paragraph
        /// </summary>
        /// <param name="element"></param>
        /// <param name="wd"></param>
        /// <returns>the level information returned</returns>
        public static Level GetParagraphLevel(Paragraph element, WordprocessingDocument wd, HostApplications ha = HostApplications.MsOffice2010)
        {
            var numDefPt = wd.MainDocumentPart.NumberingDefinitionsPart;
            Numbering numbering = numDefPt.Numbering;
            IEnumerable<AbstractNum> abstrNumCollection = null;
            IEnumerable<NumberingInstance> instNumCollection = null;
            if (numbering != null)
            {
                abstrNumCollection = numbering.ChildElements.Where<OpenXmlElement>(o =>
                {
                    return o.GetType() == typeof(AbstractNum);
                }).Cast<AbstractNum>();
                instNumCollection = numbering.ChildElements.Where<OpenXmlElement>(o =>
                {
                    return o.GetType() == typeof(NumberingInstance);
                }).Cast<NumberingInstance>();
            }
            Dictionary<string, Style> styleDict = ParagraphPropertiesHelper.FastGetStyleDict(wd, ha);
            NumberingProperties numberingProperties = element.ParagraphProperties.NumberingProperties;
            var refId = numberingProperties.NumberingId.Val;
            var refilvlVal = numberingProperties.NumberingLevelReference.Val;
            var numDefPart = wd.MainDocumentPart.NumberingDefinitionsPart;
            if (instNumCollection != null && abstrNumCollection != null)
            {
                var correspondingNumIns = instNumCollection.FirstOrDefault<NumberingInstance>(o =>
                {
                    return refId == o.NumberID.Value;
                });
                if (correspondingNumIns != null)
                {
                    AbstractNum abstractNum;
                    Level lvl = ParagraphNumbering.GetResolvedLevel(refId, refilvlVal, abstrNumCollection, instNumCollection, styleDict, out abstractNum);
                    if (lvl != null)
                    {
                        return lvl;
                    }
                }
            }
            return null;
        }
    }
}
